<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>TEST Particle Swarm Optimization</title>
    <style type="text/css">
        body {
            background-image:  url("images/desert_sand.jpg");
            background-repeat: no-repeat;
            background-size: 100%;
        }
    </style>
</head>
<body onkeydown="pso.moveGoalNode(event)" onload="pso.startSimulation()">
    <h1 style="text-align: center;color: #000000;">
        Particle Swarm Optimization - Dynamic Goal Seeking
        <br/>(Use arrow keys to move the target)
    </h1>

    <div id="tableau" style="position:fixed;right:10px;top:10px;color: black;background-color: white; border-color:black; border-style:solid; border-width: 1px">
        <em>Target X:</em> <span id="targetX"></span> <br/>
        <em>Target Y:</em> <span id="targetY"></span> <br/>
        <em>Target Z:</em> <span id="targetZ"></span><br/>
        <em>Best X:</em> <span id="bestX"></span> <br/>
        <em>Best Y:</em> <span id="bestY"></span> <br/>
        <em>Best Z:</em> <span id="bestZ"></span>
    </div>

    <script type="text/javascript"  src="PSO.js"></script>
    <script type="text/javascript">

        /**
         * array of test particles - used to access them for movement
         * @type {Array}
         */
        pso.particles = [];

        /**
         * determines the effect of current velocity on the next velocity value
         * @type {Number}
         */
        pso.defaultInertia = 0.55;

        /**
         * Max # of runs before we quit
         * @type {Number}
         */
        pso.maxRuns = 10000;

         /**********************************   Test Setup      ****************************************************************
         * vector will be 3-d space represented by x,y and z
         * scoring function will evaluate based on distance from target location = predefined spot of {x: 300, y:100, z: -10 }
         **********************************************************************************************************************/
        pso.Vector = function() {
            // vector for 3-d motion, could be n-dimensional
            this.x = {
             result: 0, value : 0, runsMissingGoal:0
             };
             this.y = {
             result: 0, value : 0, runsMissingGoal:0
             };
             this.z = {
             result: 0, value : 0, runsMissingGoal:0
             };
        };

        /**
         * vector instance to hold global best score
         */
        pso.gbest = new pso.Vector();

        /**
         * Sample implementation of a success evaluation function
         *
         * @param vector   position vector, in this case for 3 dimensions
         * @return {Object} scoring vector (position vector + scores for each dimension)
         */
        pso.successFn  = function(vector) {
            var dx = Math.sqrt(Math.pow(vector.x - pso.targetX, 2));
            var dy = Math.sqrt(Math.pow(vector.y - pso.targetY, 2));
            var dz = Math.sqrt(Math.pow(vector.z - pso.targetZ, 2));

            return {
                x: {result: dx == 0? Infinity : 1/dx, value: vector.x},
                y: {result: dy == 0? Infinity : 1/dy, value: vector.y},
                z: {result: dz == 0? Infinity : 1/dz, value: vector.z}
            };

        };

        /**
         * override default
         * @param pbest
         * @return {Boolean}
         */
        pso.isGoodEnough = function(pbest) {
            var delta = Math.abs(pbest.x.value-pso.targetX) + Math.abs(pbest.y.value- pso.targetY) + Math.abs(pbest.z.value - pso.targetZ);
            return delta < 0.5;
        };

        /**
         * goal parameters for testing
         */
        pso.targetX = 800*Math.random();
        pso.targetY = 700*Math.random();
        pso.targetZ = -10;

        /**
         * DOM Node factory function used to create particle representation as a DOM node in a web page
         *
         * @param id - id of the node
         * @return {Element}
         */
        pso.createParticleNode = function (id) {
            var node = document.createElement("img");
            node.setAttribute("id", id);
            node.setAttribute("src","images/ant.gif");
            node.setAttribute("width", "39px");
            node.setAttribute("height", "32px");
            pso.setPosition(node, {x:Math.random()* 1024, y:Math.random()*768, z:1});
            document.body.appendChild(node);
            return node;
        };

        /**
         *  moves the particle around in 3-dimensional space on a page
         *
         * @param node - particle node to move
         * @param pos position vector
         */
        pso.setPosition = function(node, pos) {
            var zIndex = Math.floor(pos.z);
            node.setAttribute("style", "z-index:" + zIndex + ";position: absolute; top:" + pos.y + "px;left: " + pos.x + "px;");
            if(node.getAttribute("id") === "goal") {
                var targetXElement = document.getElementById("targetX");
                var targetYElement = document.getElementById("targetY");
                var targetZElement = document.getElementById("targetZ");
                targetXElement.innerHTML = pos.x+100; // half of target image width is added to center
                targetYElement.innerHTML = pos.y+100; // half of target image height is added to center
                targetZElement.innerHTML = pso.targetZ;
            }
        };

        /**
         * Function to move goal node, thereby changing the goal parameters
         *
         * @param e DOM Event Object
         */
        pso.moveGoalNode = function(e){
            var id = e.keyIdentifier || e.keyCode;
            switch(id) {
                case 'Down':
                case 40:
                    pso.targetY+=5;
                    pso.globalAcceleration+=0.001;
                    break;
                case 'Up':
                case 38:
                    pso.targetY-=5;
                    pso.globalAcceleration+=0.001;
                    break;
                case 'Left':
                case 37:
                    pso.targetX-=5;
                    pso.globalAcceleration+=0.001;
                    break;
                case 'Right':
                case 39:
                    pso.targetX+=5;
                    pso.globalAcceleration+=0.001;
                    break;
                default:
                    break;
            }
            pso.setPosition(pso.goalNode, {x:pso.targetX-100, y:pso.targetY-100,z: 99});

        };

        /**
         *  function that triggers test runs; gets called on document load
         */
        pso.startSimulation = function() {

            // set up target(goal) DOM Node
            (function(){
                var goalNode = document.createElement("img");
                goalNode.setAttribute("src", "images/honey.png");
                goalNode.setAttribute("width", "200px");
                goalNode.setAttribute("height", "200px");
                goalNode.setAttribute("id", "goal");
                pso.setPosition(goalNode, {x:pso.targetX-100, y:pso.targetY-100, z:99});
                document.body.appendChild(goalNode);
                pso.goalNode = goalNode;
            })();

            // number of particles to generate
            var numParticles = 80;

            for(var i=1; i<=numParticles ; i++) {
                var p = new pso.Particle(new pso.Vector(), pso.successFn, pso.defaultInertia, pso.defaultLocalAcceleration, pso.createParticleNode("p" + i));
                pso.particles.push(p);
                p.start(pso.maxRuns);
            }

            // timeout value
            var t1=1023;

            // check if simulation is finished
            var checkInt = setInterval( function() {
                if(pso.isGoodEnough(pso.gbest)) {
                    clearInterval(checkInt);
                    pso.log("Goal found after " + pso.totalRuns + " runs. Gbest values are: x: " + pso.gbest.x.value + ", y: " + pso.gbest.y.value ,4);
                    pso.totalRuns = 0;
                }
            }, t1);
        };

        var bestXElement = document.getElementById("bestX");
        var bestYElement = document.getElementById("bestY");
        var bestZElement = document.getElementById("bestZ");

        // @implementation
        pso.broadcastResults = function(dim, value) {
            if(dim=="x") bestXElement.innerHTML = value;
            if(dim=="y") bestYElement.innerHTML = value;
            if(dim=="z") bestZElement.innerHTML = value;
        }
    </script>
</body>
</html>